<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Pools;

use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommTime;
use Srv\Libs\Exception\PoolItemException;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use Swoole\Coroutine\WaitGroup;

abstract class PoolAbstract
{
    private string $name                        = '';           // 名字
    private int $lastCheckTime                  = 0;            // 上次检测时间[秒]
    protected ?Channel $unUseChannel            = null;         // 对象数组
    private int $itemId                         = 0;            // 对象Id
    protected int $useNum                       = 0;            // 正在使用的个数
    protected int $checkTime                    = 30;           // 心跳间隔时长[秒][0-不检查]
    protected int $recycleTime                  = 600;          // 回收未使用最大时长[秒]
    protected int $waitMilliTime                = 0;            // 等待未使用最大时长[毫秒][0-永远等待]
    protected int $startNum                     = 1;            // 初始个数
    protected int $minNum                       = 1;            // 保持最少个数
    protected int $maxNum                       = 1;            // 保持最大个数

    /**
     * PoolAbstract constructor.
     * @param string $name
     * __construct
     */
    public function __construct(string $name)
    {
        $this->unUseChannel             = new Channel($this->maxNum);
        $this->itemId                   = 0;
        $this->useNum                   = 0;
        $this->name                     = $name;
        $this->init();
    }

    /**
     * __destruct
     */
    public function __destruct()
    {
        while(!$this->unUseChannel->isEmpty()){
            $PoolItem       = $this->unUseChannel->pop(0.001);
            if(!($PoolItem instanceof PoolItem)) continue;
            $this->recycleResult($PoolItem);
        }
    }

    /**
     * @return int
     * getItemId
     */
    final protected function getItemId():int
    {
        if($this->itemId >= pow(2, 31)) $this->itemId = 0;
        return ++$this->itemId;
    }

    /**
     * @return void
     * init
     */
    private function init():void
    {
        $WaitGroup          = new WaitGroup();
        $indexNum           = 0;
        while(++$indexNum <= $this->startNum){
            Coroutine::create(function()use($WaitGroup){
                $WaitGroup->add(1);
                $PoolItem   = $this->getNewPoolItem(false);
                if($PoolItem instanceof PoolItem) $this->unUseChannel->push($PoolItem);
                $WaitGroup->done();
            });
        }
        $WaitGroup->wait();
    }

    /**
     * @return bool
     * check
     */
    final public function check():bool
    {
        $this->lastCheckTime    = CommTime::getTimeStamp(0);
        $unUseList              = [];
        while(!$this->unUseChannel->isEmpty()){
            $unUseList[]        = $this->unUseChannel->pop(0.001);
        }
        if(count($unUseList) > 0) foreach($unUseList as $PoolItem){
            if(!($PoolItem instanceof PoolItem)) continue;
            if($PoolItem->isRecycle($this->recycleTime)){       // 回收
                $this->recycleResult($PoolItem);
            }else if($PoolItem->isCheck($this->checkTime)){     // 检查
                if($this->checkResult($PoolItem)){
                    $PoolItem->updateCheckTime();
                    $this->unUseChannel->push($PoolItem);
                }else{
                    $this->recycleResult($PoolItem);
                }
            }else{
                $this->unUseChannel->push($PoolItem);
            }
        }
        // 补充至最小连接池个数
        $diffNum        = $this->minNum - ($this->useNum + $this->unUseChannel->length());
        while(--$diffNum >= 0){
            $PoolItem   = $this->getNewPoolItem(false);
            if($PoolItem instanceof PoolItem) $this->unUseChannel->push($PoolItem);
        }
        return true;
    }

    /**
     * @param PoolItem $PoolItem
     * @return bool
     * freePoolItem
     */
    final public function freePoolItem(PoolItem $PoolItem):bool
    {
        if(!$PoolItem->isUse()) return true;
        $PoolItem->freed();
        --$this->useNum;
        $this->unUseChannel->push($PoolItem);
        return true;
    }

    /**
     * @param bool $isAutoFree
     * @return PoolItem
     * @throws PoolItemException
     * getPoolItem
     */
    final public function getPoolItem(bool $isAutoFree):PoolItem
    {
        if($this->unUseChannel->isEmpty() && $this->useNum < $this->maxNum){
            $PoolItem   = $this->getNewPoolItem(true);
            if($PoolItem instanceof PoolItem){
                ++$this->useNum;
                if($isAutoFree) Coroutine::defer(function()use($PoolItem){ $this->freePoolItem($PoolItem); });
                return $PoolItem;
            }
        }
        if($this->useNum > 0 || !$this->unUseChannel->isEmpty()){
            $PoolItem   = $this->unUseChannel->pop($this->waitMilliTime > 0 ? $this->waitMilliTime/1000 : -1);
            if($PoolItem instanceof PoolItem){
                $PoolItem->used();
                ++$this->useNum;
                if($isAutoFree) Coroutine::defer(function()use($PoolItem){ $this->freePoolItem($PoolItem); });
                return $PoolItem;
            }
        }
        throw new PoolItemException(CommJson::encodeArray(['msg' => 'PoolItem get failed', 'isAutoFree' => $isAutoFree, 'status' => $this->getStatus()]), -1);
    }

    /**
     * @return array
     * getStatus
     */
    final public function getStatus():array
    {
        return [
            'name'          => $this->name,
            'use'           => $this->useNum,
            'unUse'         => $this->unUseChannel->length(),
            'start'         => $this->startNum,
            'min'           => $this->minNum,
            'max'           => $this->maxNum,
            'check'         => $this->checkTime,
            'recycle'       => $this->recycleTime,
            'wait'          => $this->waitMilliTime !== 0 ? $this->waitMilliTime/1000 : 0,
            'lastTime'      => CommTime::getString('H:i:s', $this->lastCheckTime),
        ];
    }

    // getNewPoolItem
    abstract protected function getNewPoolItem(bool $isUse);
    // recycleResult
    abstract protected function recycleResult(PoolItem $PoolItem):bool;
    // checkResult
    abstract protected function checkResult(PoolItem $PoolItem):bool;
}